﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using AutoMapper;
using PrearrangedChaos.AgileZen.Service.Model;
using PrearrangedChaos.AgileZen.Service.Rest;
using Configuration = PrearrangedChaos.AgileZen.Service.Rest.Configuration;

namespace PrearrangedChaos.AgileZen.Service
{
    public class ZenService : IZenService
    {
        public int PageSize { get; set; }
        public string ApiKey { get; set; }
        private static readonly int DefaultPageSize;
        private static readonly string DefaultApiKey;

        static ZenService()
        {
            try
            {
                Configuration config = ConfigurationManager.GetSection("prearrangedChaos/zenRestConfiguration") as Configuration;

                DefaultPageSize = config.DefaultPageSize;
                DefaultApiKey = config.DefaultApiKey;
            }
            catch (ConfigurationErrorsException ex)
            {
                DefaultPageSize = 100;
            }

            Mapper.CreateMap<ITask, Task>();
            Mapper.CreateMap<ITag, Tag>();
        }

        public ZenService()
        {
            this.PageSize = DefaultPageSize;
            this.ApiKey = DefaultApiKey;
        }

        public IAttachment GetAttachment(int projectId, int storyId, int attachmentId)
        {
            return GetResource<Attachment>(new 
            { 
                ProjectId = projectId.ToString(), 
                StoryId =  storyId.ToString(), 
                AttachmentId = attachmentId.ToString()
            });
        }

        public IComment GetComment(int projectId, int storyId, int commentId)
        {
            return GetResource<Comment>(new 
            {
                ProjectId = projectId.ToString(),
                StoryId = storyId.ToString(),
                CommentId = commentId.ToString()
            });
        }

        public IInvite GetInvite(int projectId, int inviteId)
        {
            return GetResource<Invite>(new 
            {
                ProjectId = projectId.ToString(), 
                IntiveId = inviteId.ToString()
            });
        }
        
        public IUser GetMe()
        {
            return GetResource<User>();
        }

        public IUser GetMe(Action<MeOptions> options)
        {
            MeOptions meOptions = new MeOptions();
            options.Invoke(meOptions);

            return GetResource<User>(meOptions);
        }

        public IPhase GetPhase(int projectId, int phaseId)
        {
            return GetResource<Phase>(new
            {
                ProjectId = projectId.ToString(),
                PhaseId = phaseId.ToString()
            });
        }

        public IPhase GetPhase(int projectId, int phaseId, Action<PhaseOptions> options)
        {
            PhaseOptions phaseOptions = new PhaseOptions();
            options.Invoke(phaseOptions);

            return GetResource<Phase>(new 
            {
                Enrichments = phaseOptions.Enrichments,
                ProjectId = projectId.ToString(), 
                PhaseId = phaseId.ToString()
            });
        }

        public IProject GetProject(int projectId)
        {
            return GetResource<Project>(new 
                {
                    ProjectId = projectId.ToString()
                });
        }

        public IProject GetProject(int projectId, Action<ProjectOptions> options)
        {
            ProjectOptions projectOptions = new ProjectOptions();
            options.Invoke(projectOptions);

            return GetResource<Project>(new 
                {
                    Enrichments = projectOptions.Enrichments,
                    ProjectId = projectId.ToString()
                });
        }

        public IRole GetRole(int projectId, int roleId)
        {
            return GetResource<Role>(new {
                ProjectId = projectId.ToString(), 
                RoleId = roleId.ToString()
            });
        }

        public IStory GetStory(int projectId, int storyId, Action<StoryOptions> options)
        {
            StoryOptions storyOptions = new StoryOptions();
            options.Invoke(storyOptions);

            return GetResource<Story>(new 
                {
                    Enrichments = storyOptions.EnrichmentsFormatted,
                    ProjectId = projectId.ToString(), 
                    StoryId = storyId.ToString()
                });
        }

        public IStory GetStory(int projectId, int storyId)
        {
            return GetResource<Story>(new 
                {
                    ProjectId = projectId.ToString(), 
                    StoryId = storyId.ToString()
                });
        }

        public ITag GetTag(int projectId, int tagId)
        {
            return GetResource<Tag>(new 
                {
                    ProjectId = projectId.ToString(), 
                    TagId = tagId.ToString()
                });
        }

        public ITag GetTag(int projectId, string tag)
        {
            return GetResource<Tag>(new 
                {
                    ProjectId = projectId.ToString(), 
                    TagId = tag
                });
        }

        public ITag GetTag(int projectId, int tagId, Action<TagOptions> options)
        {
            TagOptions tagOptions = new TagOptions();
            options.Invoke(tagOptions);

            return GetResource<Tag>(new 
                {
                    Enrichments = tagOptions.Enrichments,
                    ProjectId = projectId.ToString(), 
                    TagId = tagId.ToString()
                });
        }

        public ITag GetTag(int projectId, string tag, Action<TagOptions> options)
        {
            TagOptions tagOptions = new TagOptions();
            options.Invoke(tagOptions);

            return GetResource<Tag>(new 
                {
                    Enrichments = tagOptions.Enrichments,
                    ProjectId = projectId.ToString(), 
                    Tag = tag
                });
        }

        public ITask GetTask(int projectId, int storyId, int taskId)
        {
            return GetResource<Task>(new 
                {
                    ProjectId = projectId.ToString(), 
                    StoryId = storyId.ToString(), 
                    TaskId = taskId.ToString()
                });
        }

        public IZenAttachmentEnumerable GetAttachments(int projectId, int storyId)
        {
            return new ZenAttachmentCollection<AttachmentCollection>(this.ApiKey, this.PageSize, projectId, storyId);
        }

        public IZenCommentEnumerable GetComments(int projectId, int storyId)
        {
            return new ZenCommentCollection<CommentCollection>(this.ApiKey, this.PageSize, projectId, storyId);
        }

        public IZenInviteEnumerable GetInvites(int projectId)
        {
            return new ZenInviteCollection<InviteCollection>(this.ApiKey, this.PageSize, projectId);
        }

        public IZenPhaseEnumerable GetPhases(int projectId)
        {
            return new ZenPhaseCollection<PhaseCollection>(this.ApiKey, this.PageSize, projectId);
        }

        public IZenProjectEnumerable GetProjects()
        {
            return new ZenProjectCollection<ProjectCollection>(this.ApiKey, this.PageSize);
        }

        public IZenUserEnumerable GetProjectMembers(int projectId)
        {
            return new ZenUserCollection<ProjectMembersCollection>(this.ApiKey, this.PageSize, projectId);
        }

        public IZenRoleEnumerable GetRoles(int projectId)
        {
            return new ZenRoleCollection<RoleCollection>(this.ApiKey, this.PageSize, projectId);
        }

        public IZenUserEnumerable GetRoleMembers(int projectId, int roleId)
        {
            return new ZenUserCollection<RoleMembersCollection>(this.ApiKey, this.PageSize, projectId, roleId);
        }

        public IZenStoryEnumerable GetStories(int projectId)
        {
            return new ZenStoryCollection<StoryCollection>(this.ApiKey, this.PageSize, projectId);
        }

        public IZenStoryEnumerable GetMyStories()
        {
            return new ZenStoryCollection<MyStoriesCollection>(this.ApiKey, this.PageSize);
        }

        public IZenTagEnumerable GetStoryTags(int storyId)
        {
            return new ZenTagCollection<StoryTagsCollection>(this.ApiKey, this.PageSize, storyId);
        }

        public IZenTagEnumerable GetTags(int projectId)
        {
            return new ZenTagCollection<TagCollection>(this.ApiKey, this.PageSize, projectId);
        }

        public IZenStoryEnumerable GetTagStories(int projectId, int tagId)
        {
            return new ZenStoryCollection<TagStoriesCollection>(this.ApiKey, this.PageSize, projectId, tagId);
        }

        public IZenStoryEnumerable GetTagStories(int projectId, string tag)
        {
            return new ZenStoryCollection<TagStoriesCollection>(this.ApiKey, this.PageSize, projectId, tag);
        }

        public IZenTaskEnumerable GetTasks(int projectId, int storyId)
        {
            return new ZenTaskCollection<TaskCollection>(this.ApiKey, this.PageSize, projectId, storyId);
        }

        public IStory CreateStory(
            int projectId,
            string text,
            string details = null,
            string size = null,
            StoryColor color = StoryColor.Grey,
            int? phaseId = null,
            int? ownerId = null,
            DateTime? deadline = null,
            IEnumerable<ITag> tags = null,
            IEnumerable<ITask> tasks = null)
        {
            Story newStory = new Story();
            newStory.Text = text;
            newStory.Details = details;
            newStory.Size = size;
            newStory.Color = color;
            if (phaseId != null)
                newStory.Phase = new Phase() { Id = phaseId.Value };
            if (ownerId != null)
                newStory.Owner = new User() { Id = ownerId.Value };
            newStory.Deadline = deadline;
            newStory.TagsInternal = tags == null ? null : Mapper.Map<ITag[], Tag[]>(tags.ToArray());
            newStory.TasksInternal = tasks == null ? null : Mapper.Map<ITask[], Task[]>(tasks.ToArray());

            return this.Create<Story>(newStory, new { ProjectId = projectId });
        }

        public IRole CreateRole(
            int projectId,
            string name,
            ApiAccess access)
        {
            Role newRole = new Role();
            newRole.Name = name;
            newRole.Access = access;

            return this.Create<Role>(newRole, new { ProjectId = projectId });
        }

        public void AddUserToRole(int projectId, int roleId, int userId)
        {
            User userToAdd = new User();
            userToAdd.Id = userId;

            this.Add<RoleMembersCollection, User>(userToAdd, new 
                {
                    ProjectId = projectId.ToString(), 
                    RoleId = roleId.ToString()
                });
        }

        public ITask CreateTask(
            int projectId,
            int storyId,
            string text,
            TaskStatus status = TaskStatus.Incomplete)
        {
            Task newTask = new Task();
            newTask.Text = text;
            newTask.Status = status;

            return this.Create<Task>(newTask, new 
                {
                    ProjectId = projectId.ToString(), 
                    StoryId = storyId.ToString()
                });
        }

        public ITag CreateTag(
            int projectId,
            string name)
        {
            Tag newTag = new Tag();
            newTag.Name = name;

            return this.Create<Tag>(newTag, new 
                {
                    ProjectId = projectId.ToString()
                });
        }

        public void AddTagToStory(int projectId, int storyId, int tagId)
        {
            Tag tagToAdd = new Tag();
            tagToAdd.Id = tagId;

            this.Add<StoryTagsCollection, Tag>(tagToAdd, new 
                {
                    ProjectId = projectId.ToString(), 
                    StoryId = storyId.ToString()
                });
        }

        public void AddUserToProject(int projectId, int userId)
        {
            User userToAdd = new User();
            userToAdd.Id = userId;

            this.Add<ProjectMembersCollection, User>(userToAdd, new 
                {
                    ProjectId = projectId.ToString()
                });
        }

        public IPhase CreatePhase(
            int projectId,
            string name,
            string description,
            int? index = null,
            int? limit = null)
        {
            Phase newPhase = new Phase();
            newPhase.Name = name;
            newPhase.Description = description;
            if (index != null)
                newPhase.Index = index.Value;
            if (limit != null)
                newPhase.Limit = limit.Value;

            return this.Create<Phase>(newPhase, new 
                {
                    ProjectId = projectId.ToString()
                });
        }

        public IInvite CreateInvite(
            int projectId,
            string email,
            int roleId)
        {
            Invite newInvite = new Invite();
            newInvite.Email = email;
            newInvite.Role = new Role() { Id = roleId };

            return this.Create<Invite>(newInvite, new 
                {
                    ProjectId = projectId.ToString()
                });
        }

        public IComment CreateComment(
            int projectId,
            int storyId,
            string text)
        {
            Comment newComment = new Comment();
            newComment.Text = text;

            return this.Create<Comment>(newComment, new 
                {
                    ProjectId = projectId.ToString(), 
                    StoryId = storyId.ToString()
                });
        }


        public IAttachment CreateAttachment(
            int projectId,
            int storyId,
            byte attachment)
        {
            Attachment newAttachment = new Attachment();

            return this.Create<Attachment>(newAttachment, new 
                {
                    ProjectId = projectId.ToString(), 
                    StoryId = storyId.ToString()
                });
        }

        public void DeleteAttachment(
            int projectId,
            int storyId,
            int attachmentId)
        {
            this.Delete<Attachment>(new 
                {
                    ProjectId = projectId.ToString(), 
                    StoryId = storyId.ToString(), 
                    AttachmentId = attachmentId.ToString()
                });
        }

        public void DeleteComment(
            int projectId,
            int storyId,
            int commentId)
        {
            this.Delete<Comment>(new 
                {
                    ProjectId = projectId.ToString(), 
                    StoryId = storyId.ToString(), 
                    CommentId = commentId.ToString()
                });
        }

        public void DeleteInvite(
            int projectId,
            int inviteId)
        {
            this.Delete<Invite>(new 
                {
                    ProjectId = projectId.ToString(), 
                    InviteId = inviteId.ToString()
                });
        }

        public void DeletePhase(
            int projectId,
            int phaseId)
        {
            this.Delete<Phase>(new 
                {
                    ProjectId = projectId.ToString(), 
                    PhaseId = phaseId.ToString()
                });
        }

        public void RemoveUserFromProject(
            int projectId,
            int userId)
        {
            this.Remove<ProjectMembersCollection>(userId, projectId.ToString());
        }

        public void DeleteTag(
            int projectId,
            int tagId)
        {
            this.Delete<Tag>(new 
                {
                    ProjectId = projectId.ToString(), 
                    TagId = tagId.ToString()
                });
        }

        public void RemoveTagFromStory(
            int projectId,
            int storyId,
            int tagId)
        {
            this.Remove<TagStoriesCollection>(tagId, new 
                {
                    ProjectId = projectId.ToString(), 
                    StoryId = storyId.ToString(),
                    TagId = tagId.ToString()
                });
        }

        public void DeleteTask(
            int projectId,
            int storyId,
            int tagId)
        {
            this.Delete<Task>(new 
                {
                    ProjectId = projectId.ToString(), 
                    StoryId = storyId.ToString(), 
                    TagId = tagId.ToString()
                });
        }

        public void DeleteRole(
            int projectId,
            int roleId)
        {
            this.Delete<Role>(new 
                {
                    ProjectId = projectId.ToString(), 
                    RoleId = roleId.ToString()
                });
        }

        public void RemoveUserFromRole(
            int projectId,
            int roleId,
            int userId)
        {
            this.Remove<RoleMembersCollection>(userId, new 
                {
                    ProjectId = projectId.ToString(), 
                    RoleId = roleId.ToString()
                });
        }

        public void DeleteStory(
            int projectId,
            int storyId)
        {
            this.Delete<Story>(new 
                {
                    ProjectId = projectId.ToString(), 
                    StoryId = storyId.ToString()
                });
        }

        public IAttachment UpdateAttachment(IAttachmentUpdater attachment, int projectId, int storyId)
        {
            AttachmentUpdater resourceToUpdate = GetResourceToUpdate<AttachmentUpdater, IAttachmentUpdater>(attachment);

            return this.Update<Attachment, AttachmentUpdater>(
                resourceToUpdate, 
                new 
                    {
                        ProjectId = projectId.ToString(), 
                        StoryId = storyId.ToString(), 
                        AttachmentId = attachment.Id.ToString()
                    });
        }

        public IComment UpdateComment(ICommentUpdater comment, int projectId, int storyId)
        {
            CommentUpdater resourceToUpdate = GetResourceToUpdate<CommentUpdater, ICommentUpdater>(comment);

            return this.Update<Comment, CommentUpdater>(
                resourceToUpdate, 
                new 
                    {
                        ProjectId = projectId.ToString(), 
                        StoryId = storyId.ToString(), 
                        CommentId = comment.Id.ToString()
                    });
        }

        public IPhase UpdatePhase(IPhaseUpdater phase, int projectId)
        {
            PhaseUpdater resourceToUpdate = GetResourceToUpdate<PhaseUpdater, IPhaseUpdater>(phase);

            return this.Update<Phase, PhaseUpdater>(resourceToUpdate, new 
                {
                    ProjectId = projectId.ToString(), 
                    PhaseId = phase.Id.ToString()
                });
        }

        public ITag UpdateTag(ITagUpdater tag, int projectId)
        {
            TagUpdater resourceToUpdate = GetResourceToUpdate<TagUpdater, ITagUpdater>(tag);

            return this.Update<Tag, TagUpdater>(resourceToUpdate, new 
                {
                    ProjectId = projectId.ToString(), 
                    TagId = tag.Id.ToString()
                });
        }

        public ITask UpdateTask(ITaskUpdater task, int projectId, int storyId)
        {
            TaskUpdater resourceToUpdate = GetResourceToUpdate<TaskUpdater, ITaskUpdater>(task);

            return this.Update<Task, TaskUpdater>(resourceToUpdate, new 
                {
                    ProjectId = projectId.ToString(), 
                    StoryId = storyId.ToString(), 
                    TaskId = task.Id.ToString()
                });
        }

        public IRole UpdateRole(IRoleUpdater role, int projectId)
        {
            RoleUpdater resourceToUpdate = GetResourceToUpdate<RoleUpdater, IRoleUpdater>(role);

            return this.Update<Role, RoleUpdater>(resourceToUpdate, new 
                {
                    ProjectId = projectId.ToString(), 
                    RoleId = role.Id.ToString()
                });
        }

        public IStory UpdateStory(IStoryUpdater story, int projectId)
        {
            StoryUpdater resourceToUpdate = GetResourceToUpdate<StoryUpdater, IStoryUpdater>(story);

            return this.Update<Story, StoryUpdater>(resourceToUpdate, new 
                {
                    ProjectId = projectId.ToString(), 
                    StoryId = story.Id.ToString()
                });
        }

        public IProject UpdateProject(IProjectUpdater project)
        {
            ProjectUpdater resourceToUpdate = GetResourceToUpdate<ProjectUpdater, IProjectUpdater>(project);

            return this.Update<Project, ProjectUpdater>(resourceToUpdate, project.Id.ToString());
        }

        private TModel GetResourceToUpdate<TModel, TInterface>(TInterface attachment)
            where TModel : class, TInterface
        {
            TModel attachmentToUpdate = attachment as TModel;

            if (attachmentToUpdate == null)
            {
                Mapper.CreateMap<TInterface, TModel>();
                attachmentToUpdate = Mapper.Map<TInterface, TModel>(attachment);
            }
            return attachmentToUpdate;
        }

        private TModel GetResource<TModel>(object ids = null) 
            where TModel : class, IModel
        {
            using (ZenClient zenClient = new ZenClient(this.ApiKey))
            {
                return zenClient.Get<TModel>(ids);
            }
        }

        private TModel Create<TModel>(IModel resource, object ids) where TModel : class, IModel
        {
            using (ZenClient zenClient = new ZenClient(this.ApiKey))
            {
                return zenClient.Create<TModel>(resource, ids);
            }
        }

        private TModel Update<TModel, TModelUpdater>(TModelUpdater resource, object ids) 
            where TModelUpdater : IModelUpdater
            where TModel : class, IModel
        {
            using (ZenClient zenClient = new ZenClient(this.ApiKey))
            {
                return zenClient.Update<TModel, TModelUpdater>(resource, ids);
            }
        }

        private void Delete<TModel>(object ids) where TModel : class, IModel
        {
            using (ZenClient zenClient = new ZenClient(this.ApiKey))
            {
                zenClient.Delete<TModel>(ids);
            }
        }

        private void Add<TCollection, TModel>(TModel resource, object ids) 
            where TModel : class, IModel
            where TCollection : IPagingResponse<TModel>
        {
            using (ZenClient zenClient = new ZenClient(this.ApiKey))
            {
                zenClient.Add<TCollection, TModel>(resource, ids);
            }
        }

        private void Remove<TCollection>(int resourceId, object ids)
        {
            using (ZenClient zenClient = new ZenClient(this.ApiKey))
            {
                zenClient.Remove<TCollection>(resourceId, ids);
            }
        }
    }
}
